package comp6231.project.replica.kelvin;

import java.io.*;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import comp6231.project.common.ReplicaState;

/**
 * Class represents data structure of items and their count
 * @author Kelvin Iu
 *
 */
public class ItemsList {

	private static final int HASHTABLE_SIZE = 1000;
	private Lock[] locks = new ReentrantLock[HASHTABLE_SIZE];
	private HashMap<String, Integer>[] items = new HashMap[HASHTABLE_SIZE];
	
	public ItemsList() {
		init();
	}
	
	private void init() {
		for (int i = 0; i < HASHTABLE_SIZE; i++) {
			locks[i] = new ReentrantLock();
			items[i] = new HashMap<String, Integer>();
		}		
	}
	
	private int getLocksSlot(String key) {
		int id = 0;
		try {
			id = Integer.parseInt(key);
		} catch (NumberFormatException ex) {			
		}
		int slot = id % HASHTABLE_SIZE;
		return slot;
	}
	
	/**
	 * Perform locks on two items as atomic step
	 * @param itemID1 First item
	 * @param itemID2 Second item
	 */
	public void lockBoth(String itemID1, String itemID2) {
		int slot1 = getLocksSlot(itemID1);
		int slot2 = getLocksSlot(itemID2);
		boolean lock1 = false;
		boolean lock2 = false;
		boolean tryLock = true;
		while (tryLock) {
			try {
				lock1 = locks[slot1].tryLock();
				lock2 = locks[slot2].tryLock();
			} finally {
				if (! (lock1 && lock2)) {
	                if (lock1) {
	                	locks[slot1].unlock();
	                }
	                if (lock2) {
	                	locks[slot2].unlock();
	                }
	                try {
	                	Thread.sleep(10);
	                } catch (Exception ex) {                	
	                }
	            } else {
	            	tryLock = false;
	            }
			}
		}
	}
	
	/**
	 * Perform a lock on a specific item
	 * @param itemID Item to be locked
	 */
	public void lock(String itemID) {
		int slot = getLocksSlot(itemID);
		locks[slot].lock();		
	}
	
	/**
	 * Release the lock on a specific item
	 * @param itemID Item to be unlocked
	 */	
	public void unlock(String itemID) {
		int slot = getLocksSlot(itemID);
		locks[slot].unlock();		
	}
	
	/**
	 * Get number of items
	 * @param itemID 4-digit Item ID
	 * @return number of items
	 */
	public int getCount(String itemID) {
		int slot = getLocksSlot(itemID);
		int count = 0;
		if (items[slot].containsKey(itemID)) {
			count = items[slot].get(itemID).intValue();
		}
		return count;
	}
	
	/**
	 * Update number of items
	 * @param itemID 4-digit Item ID
	 * @param count Number of items
	 */
	public void setCount(String itemID, int count) {
		int slot = getLocksSlot(itemID);
		/*try {
			Thread.sleep(1000);
		} catch (InterruptedException ex) {			
		}*/
		items[slot].put(itemID, new Integer(count));
	}
	
	/**
	 * Load items list from disk file
	 * @param filename File
	 * @return true if successful, false otherwise
	 */
	public synchronized boolean load(String filename) {
		Properties p = new Properties();
		FileInputStream is = null;
		boolean result = false;
		try {
			is = new FileInputStream(new File(filename));
			p.load(is);
			Enumeration<Object> e = p.keys();
			while (e.hasMoreElements()) {
				try {
					String key = (String)e.nextElement();
					String val = p.getProperty(key);
					if (val != null) {
						int n = Integer.parseInt(val);
						setCount(key, n);
					}
				} catch (NumberFormatException ne) {					
				}
			}
			result = true;
		} catch (IOException ex) {
			ex.printStackTrace();
		} finally {
			if (is != null) {
				try {
					is.close();
				} catch (IOException ex) {					
				}
			}
		}
		return result;
	}
	
	/**
	 * Save items list into disk file
	 * @param filename File
	 * @return true if successful, false otherwise
	 */
	public synchronized boolean save(String filename) {
		Properties p = new Properties();
		FileOutputStream os = null;
		boolean result = false;
		try {
			for (int i = 0; i < HASHTABLE_SIZE; i++) {
				if (locks[i] != null && items[i] != null) {
					locks[i].lock();
					Iterator<String> it = items[i].keySet().iterator();
					while (it.hasNext()) {
						String key = it.next();
						String val = items[i].get(key).toString();
						p.put(key, val);
					}			
					locks[i].unlock();
				}
			}		
			os = new FileOutputStream(new File(filename));
			p.store(os, "");
			result = true;
			
		} catch (IOException ex) {
			ex.printStackTrace();
		} finally {
			if (os != null) {
				try {
					os.close();
				} catch (IOException ex) {					
				}
			}
		}
		return false;
	}
	
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("=== Items ===\n");
		for (int i = 0; i < HASHTABLE_SIZE; i++) {
			if (locks[i] != null && items[i] != null) {
				locks[i].lock();
				Iterator<String> it = items[i].keySet().iterator();
				while (it.hasNext()) {
					String key = it.next();
					sb.append(key);
					sb.append("=");
					sb.append(items[i].get(key).toString());
					sb.append("\n");
				}			
				locks[i].unlock();
			}
		}
		return sb.toString();
	}
	
	public void transferToStoreState(ReplicaState state) {
		for (int i = 0; i < HASHTABLE_SIZE; i++) {
			if (items[i] != null) {
				Iterator<String> it = items[i].keySet().iterator();
				while (it.hasNext()) {
					String itemID = it.next();
					state.addItem(itemID, items[i].get(itemID).intValue());
				}			
			}
		}		
	}
	
	public void transferFromStoreState(ReplicaState state) {
		init();
		Map<String, Integer> items = state.getItems();
		Iterator<String> it = items.keySet().iterator();
		while (it.hasNext()) {
			String itemID = it.next();
			setCount(itemID, items.get(itemID).intValue());
		}
	}	
}
